// ============================================================================
// FileName: NATKeepAliveRelay.cs
//
// Description:
// A socket that listens for agents that wish to send NAT keepalives to clients and relays the
// request from a specified socket.
//
// Author(s):
// Aaron Clauson
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2007 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
// ============================================================================

using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using log4net;
using CN.VoIP.Net.SIP;
using CN.VoIP.SIPServer;

#if UNITTEST
using NUnit.Framework;
#endif

namespace CN.VoIP.SIPServer
{
    public class NATKeepAliveMessage
    {
        public IPEndPoint SendToSocket;
        public IPEndPoint SendFromSocket;

        private NATKeepAliveMessage()
        { }

        public NATKeepAliveMessage(IPEndPoint sendToSocket, IPEndPoint sendFromSocket)
        {
            SendToSocket = sendToSocket;
            SendFromSocket = sendFromSocket;
        }

        public static NATKeepAliveMessage ParseNATKeepAliveMessage(byte[] buffer)
        {
            if (buffer != null && buffer.Length == 16)
            {
                byte[] sendToAddrBuffer = new byte[4];
                Buffer.BlockCopy(buffer, 0, sendToAddrBuffer, 0, 4);
                IPAddress sendToAddress = new IPAddress(sendToAddrBuffer);

                int sendToPort = BitConverter.ToInt32(buffer, 4);

                byte[] sendFromAddrBuffer = new byte[4];
                Buffer.BlockCopy(buffer, 8, sendFromAddrBuffer, 0, 4);
                IPAddress sendFromAddress = new IPAddress(sendFromAddrBuffer);

                int sendFromPort = BitConverter.ToInt32(buffer, 12);

                NATKeepAliveMessage natKeepAliveMsg = new NATKeepAliveMessage(new IPEndPoint(sendToAddress, sendToPort), new IPEndPoint(sendFromAddress, sendFromPort));
                return natKeepAliveMsg;
            }
            else
            {
                return null;
            }
        }

        public byte[] ToBuffer()
        {
            if (SendToSocket != null && SendFromSocket != null)
            {
                byte[] buffer = new byte[16];

                Buffer.BlockCopy(SendToSocket.Address.GetAddressBytes(), 0, buffer, 0, 4);
                Buffer.BlockCopy(BitConverter.GetBytes(SendToSocket.Port), 0, buffer, 4, 4);
                Buffer.BlockCopy(SendFromSocket.Address.GetAddressBytes(), 0, buffer, 8, 4);
                Buffer.BlockCopy(BitConverter.GetBytes(SendFromSocket.Port), 0, buffer, 12, 4);

                return buffer;
            }
            else
            {
                return null;
            }
        }

        #region Unit testing.

        #if UNITTEST

        [TestFixture]
        public class NATKeepAliveMessageUnitTest
        {
            [TestFixtureSetUp]
            public void Init()
            {

            }

            [TestFixtureTearDown]
            public void Dispose()
            {

            }

            [Test]
            public void SampleTest()
            {
                Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);
                Assert.IsTrue(true, "True was false.");
            }

            [Test]
            public void ReverseMessageTest()
            {
                Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);

                string sendToAddress = "192.168.1.1";
                int sendToPort = 3455;
                string sendFromAddress = "192.168.1.2";
                int sendFromPort = 3244;
                NATKeepAliveMessage keepAliveMsg = new NATKeepAliveMessage(new IPEndPoint(IPAddress.Parse(sendToAddress), sendToPort), new IPEndPoint(IPAddress.Parse(sendFromAddress), sendFromPort));

                byte[] buffer = keepAliveMsg.ToBuffer();
                Assert.IsTrue(buffer != null && buffer.Length == 16, "The byte buffer produced for the NATKeepAliveMessage is invalid.");

                NATKeepAliveMessage rtnMsg = NATKeepAliveMessage.ParseNATKeepAliveMessage(buffer);
                Assert.IsNotNull(rtnMsg, "The NATKeepAliveMessage could not be parsed from the buffer.");

                Assert.IsTrue(rtnMsg.SendToSocket.ToString() == keepAliveMsg.SendToSocket.ToString(), "The sent and returned sendto sockets were different.");
                Assert.IsTrue(rtnMsg.SendFromSocket.ToString() == keepAliveMsg.SendFromSocket.ToString(), "The sent and returned sendfrom sockets were different.");
            }
        }

        #endif

        #endregion
    }

    public class NATKeepAliveRelay
    {      
        private ILog logger = log4net.LogManager.GetLogger("natkeepalive");
        
        private static ProxyMonitorChannel m_monitorChannel = null;
        private SIPChannel m_natKeepAliveChannel;   // Can use a SIP Channel for this since it's essentially just a UDP listener anyway.

        private byte[] m_sendBuffer = new byte[] { 0x0, 0x0, 0x0, 0x0 };    // Doesn't matter what is sent since it's just to keep the NAT connection alive.

        /// <param name="listenerSocket">Socket to listen for NAT keepalive relay requests on.</param>
        public NATKeepAliveRelay(IPEndPoint listenerSocket, ProxyMonitorChannel proxyMonitor)
        {
            m_monitorChannel = proxyMonitor;
            m_natKeepAliveChannel = new SIPChannel(listenerSocket);
            m_natKeepAliveChannel.SIPMessageReceived += new SIPMessageReceivedDelegate(NatKeepAliveChannelMessageReceived);

            logger.Debug("NATKeepAlive Relay instantiated on " + listenerSocket + ".");
        }

        public void Shutdown()
        {
            m_natKeepAliveChannel.Close();
        }

        private void NatKeepAliveChannelMessageReceived(SIPChannel sipChannel, IPEndPoint remoteEndPoint, byte[] buffer)
        {
            try
            {
                NATKeepAliveMessage keepAliveMessage = NATKeepAliveMessage.ParseNATKeepAliveMessage(buffer);

                if (keepAliveMessage != null)
                {
                    SIPTransport.SendFrom(keepAliveMessage.SendFromSocket, keepAliveMessage.SendToSocket, m_sendBuffer);

                    ProxyMonitorEvent monitorEvent = new ProxyMonitorEvent(ProxyServerTypesEnum.StatelessProxy, ProxyEventTypesEnum.NATKeepAlive, "Sending keepalive to " + keepAliveMessage.SendToSocket.ToString() + " from proxy socket " + keepAliveMessage.SendFromSocket.ToString(), null);
                    SendMonitorEvent(monitorEvent);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception NatKeepAliveChannelMessageReceived. " + excp.Message);
            }
        }

        private static void SendMonitorEvent(ProxyMonitorEvent monitorEvent)
        {
            if (m_monitorChannel != null)
            {
                m_monitorChannel.Send(monitorEvent);
            }
        }
    }
}
